package io.mft.commons.db;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.db.Entity;
import cn.hutool.db.dialect.Dialect;
import cn.hutool.db.dialect.DialectFactory;
import cn.hutool.db.dialect.DialectName;
import cn.hutool.json.JSONUtil;
import io.mft.commons.config.ConfigProvider;
import io.mft.commons.db.mapper.SQLExecutorDbMapper;
import io.mft.commons.db.mapper.exception.MapperException;
import io.mft.commons.db.source.DbProvider;
import io.mft.commons.exception.MFTException;
import io.mft.commons.function.response.kv.KVMap;
import io.mft.commons.util.CamelToUnderlineUtil;
import lombok.extern.slf4j.Slf4j;
/**
 * Mybatise  SQL解析工具，用于获取MyvatisSQL
 * @类型名称：MybatisDbUtil
 * @说明：TODO
 * @创建者: 胡常建  
 * @创建时间: 2018年2月28日 下午5:43:28
 * @修改者: 胡常建  
 * @修改时间: 2018年2月28日 下午5:43:28
 */
@Slf4j
public class DbUtil extends MFTMyBatisConfigParser{
	static Logger logger=LoggerFactory.getLogger(DbUtil.class);
	private static final String SEQUENCE_PREFIX = "seq";
	
	private static final String TABLE_PREFIX = "t_";
	
	private static final int SEQUENCE_LENTH_LIMIT = 30;
    private static final String SEPARATE = "^";
    private Class<?> implClass;
   // private Map<String, String> mapperXmlCache=new ConcurrentHashMap<String, String>();
    public DbUtil(Class<?> implClass){
    	this.implClass=implClass;
    }
    /**
     * 加载Mybatis解析的SQL语句以及解析的SQL参数
     * @方法名称: loadSQL
     * @参数 @param implClass   SQL数据服务实现类型，此实现类型与同级sql包中的sql文件名称相同
     * @参数 @param sqlId   
     * @参数 @param parameter
     * @参数 @return
     * @说明: TODO
     * @返回类型 SQL    
     * @创建者: 胡常建  
     * @创建时间: 2018年2月28日 下午5:38:21
     * @修改者: 胡常建  
     * @修改时间: 2018年2月28日 下午5:38:21
     */
    public static SQL loadSQL(Class<?> implClass,String sqlId,Map parameter) {
    	DbUtil dbUtil=new DbUtil(implClass);
		SQL sql=dbUtil.loadSQL(sqlId, parameter);
		String isPrintSQLText=ConfigProvider.get("db.isPrintSQLText", "0");
		if ("1".equals(isPrintSQLText)) {
			logger.info("\nsqlId:\n"+implClass.getName()+"->>>"+sqlId+"\nsqlText:\n"+sql.getSql()+"\n参数:\n"+JSONUtil.toJsonStr(sql.getArgs()));
		}
		return sql;
	}
    /**
     * 描述：根据sqlId,datasourceId设置mybatis文件缓存
     * @author 岳知之
     * @created 2016年12月5日 下午9:07:44
     * @since 
     * @param sqlId 命名空间id
     * @param datasourceId 数据源ID  
     * @return
     */
    private synchronized  void setMyBatisSql(String sqlId)
    {
        String nsKey = getNsKey(sqlId);
        //如果初始化，直接返回
        if ( isInitialized(nsKey) )
        {
            return;
        }
        if ( ObjectUtil.isNotEmpty(sqlId) )
        {
            //获取文件路径
            String ns = sqlId.substring(0, sqlId.lastIndexOf("."));//去掉最后的文件内的id，得到命名空间
            String filePath = getMapperFilePath(ns);
            
            //根据文件路径读取文件并写入缓存
            InputStream in = implClass.getClassLoader().getResourceAsStream(filePath);
            if ( in == null )
            {
                throw new RuntimeException("sqlId=[" + sqlId + "]的文件读取失败,请检查配置文件" + filePath + "是否正确");
            }
            
            Map<String, MyBatisSqlInfo> myBatisSqlMap = parseMyBatisXmlByFile(in, "");
            for (Iterator<String> iterator = myBatisSqlMap.keySet().iterator(); iterator.hasNext();)
            {
                String fileSqlId = (String) iterator.next();
                String sqlIdKey = new StringBuilder(ns).append(".").append(fileSqlId).toString();
                getCache().put(getNsKey(sqlIdKey), myBatisSqlMap.get(fileSqlId));
            }
        }
    }
    
    /**
     * 描述：获取mapper配置文件的路径
     * @author yuezz
     * @created 2017年3月29日 下午5:38:44
     * @since 
     * @param ns
     * @return
     */
    private  String getMapperFilePath(String ns)
    {
        return new StringBuilder().append(ns.replace(".", "/")).append(".xml").toString();
    }
    
    /**
     * 描述：通过命名空间 sqlId和 datasourceId和输入参数获取一个SqlInfo对象
     * 
     * @author yuezz
     * @created 2016年9月14日 上午10:37:17
     * @since
     * @param sqlId
     *            命名空间ID
     * @param datasourceId
     *            数据库键值
     * @param param
     *            调用sql的参数
     * @return
     */
	private  SqlInfo getSqlInfo(String sqlId, Map param)
    {
    	sqlId=new StringBuffer(implClass.getPackage().getName()).append(".").append(implClass.getSimpleName()).append(".").append(sqlId).toString();
        SqlInfo sqlInfo = null;
        String nsKey = getNsKey(sqlId);
        //如果没有初始化，先初始化文件数据到缓存
        if ( !isInitialized(nsKey) )
        {
            setMyBatisSql(sqlId);
        }
        
        MyBatisSqlInfo myBatisSqlInfo = getCache().get(nsKey);
        if ( myBatisSqlInfo != null )
        {
            sqlInfo = assembleSqlInfo(param, myBatisSqlInfo);
        }
        else
        {
            throw new RuntimeException("该sql可能没有被初始化成功,sqlId=[" + sqlId  + "],请检查配置文件是否正确");
        }
        
        return sqlInfo;
    }
    private  boolean isInitialized(String nskey)
    {
        return getCache().containsKey(nskey);
    }
    
    private  String getNsKey(String sqlId)
    {
        return new StringBuilder().append(SEPARATE).append(sqlId).toString();
    }
    /**
     * 加载SQL
     * @方法名称: loadSQL
     * @参数 @param sqlId
     * @参数 @param parameter
     * @参数 @return
     * @说明: TODO
     * @返回类型 SQL    
     * @创建者: 胡常建  
     * @创建时间: 2018年2月28日 下午5:37:59
     * @修改者: 胡常建  
     * @修改时间: 2018年2月28日 下午5:37:59
     */
    private  SQL loadSQL(String sqlId,Map parameter) {
    	SqlInfo sqlInfo=getSqlInfo(sqlId, parameter);
        String sql = sqlInfo.getSql();
        List<String> valueList = sqlInfo.getValueList();
        Object[] args = valueList.toArray();
        String[] keys=new String[args.length];
        sqlInfo.getParamNameList().toArray(keys);
        SQL t=new SQL(sql, keys,args);
        return t;
	}

	
	
	public static KVMap formatToUnderline(KVMap data) {
		Set<?> keys=data.keySet();
		KVMap t=new KVMap();
		for (Object object : keys) {
			String tk=CamelToUnderlineUtil.camelToUnderline(String.valueOf(object));
			t.set(tk, data.get(String.valueOf(object)));
		}
		return t;
	}
	 private void parseMapperXml(String ns,String mapperXml) {
	    	/**boolean s=false;
	    	if (mapperXmlCache.containsKey(ns)) {
				if (!mapperXmlCache.get(ns).equals(mapperXml)) {
					mapperXmlCache.put(ns, mapperXml);
					s=true;
				}
			}else {
				mapperXmlCache.put(ns, mapperXml);
				s=true;
			}
	    	if (s) {
	        	InputStream inputStrem = new ByteArrayInputStream(mapperXml.getBytes()); 
	        	Map<String, MyBatisSqlInfo> myBatisSqlMap = parseMyBatisXmlByFile(inputStrem, "");
	            for (Iterator<String> iterator = myBatisSqlMap.keySet().iterator(); iterator.hasNext();)
	            {
	                String fileSqlId = (String) iterator.next();
	                String sqlIdKey = new StringBuilder(ns).append(".").append(fileSqlId).toString();
	                getCache().put(getNsKey(sqlIdKey), myBatisSqlMap.get(fileSqlId));
	            }
			}
			**/
        	InputStream inputStrem = new ByteArrayInputStream(mapperXml.getBytes()); 
        	Map<String, MyBatisSqlInfo> myBatisSqlMap = parseMyBatisXmlByFile(inputStrem, "");
            for (Iterator<String> iterator = myBatisSqlMap.keySet().iterator(); iterator.hasNext();)
            {
                String fileSqlId = (String) iterator.next();
                String sqlIdKey = new StringBuilder(ns).append(".").append(fileSqlId).toString();
                getCache().put(getNsKey(sqlIdKey), myBatisSqlMap.get(fileSqlId));
            }
		}

	public void parseSQL(String sqlId, String sqlText) {
		String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!DOCTYPE mapper  PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\"> <mapper> <sql id=\""
				+ sqlId + "\"> <![CDATA[ " + sqlText + " ]]> </sql></mapper>";
		parseMapperXmlSQL(xml);
	}
	public void parseMapperXmlSQL(String mapperXml) {
		String ns = implClass.getName();
		parseMapperXml(ns, mapperXml);
	}
    public SQL loadParseSQL(String sqlId,Map<String,Object> parameter) {
    	String ns=implClass.getName();
		String sqlIdKey = new StringBuilder(ns).append(".").append(sqlId).toString();
		String nsKey=getNsKey(sqlIdKey);
        MyBatisSqlInfo myBatisSqlInfo = getCache().get(nsKey);
        if ( myBatisSqlInfo != null )
        {
        	SqlInfo sqlInfo = assembleSqlInfo(parameter, myBatisSqlInfo);
            String sql = sqlInfo.getSql();
            List<String> valueList = sqlInfo.getValueList();
            Object[] args = valueList.toArray();
            String[] keys=new String[args.length];
            sqlInfo.getParamNameList().toArray(keys);
            SQL t=new SQL(sql, keys,args);
    		String isPrintSQLText=ConfigProvider.get("db.isPrintSQLText", "0");
    		if ("1".equals(isPrintSQLText)) {
    			logger.info("\nsqlId:\n"+implClass.getName()+"->>>"+sqlId+"\nsqlText:\n"+t.getSql()+"\n参数:\n"+JSONUtil.toJsonStr(t.getArgs()));
    		}
            return t;
        }
        else
        {
            throw new RuntimeException("该sql可能没有被初始化成功,sqlId=[" + sqlId  + "],请检查");
        }
	}

	private static String getSequenceNameByTableName(String tableName) {
		if (ObjectUtil.isNotEmpty(tableName)) {
			String sequenceName = tableName.toLowerCase();
			if (sequenceName.startsWith(TABLE_PREFIX)) {
				sequenceName = sequenceName.replaceFirst("^t", SEQUENCE_PREFIX);
			} else if (sequenceName.startsWith(SEQUENCE_PREFIX + "_")) {
			} else {
				sequenceName = SEQUENCE_PREFIX + "_" + sequenceName;
			}
			return sequenceName.length() < SEQUENCE_LENTH_LIMIT ? sequenceName: sequenceName.substring(0, SEQUENCE_LENTH_LIMIT);
		} else {
			throw new MFTException("tableName is null!");
		}
	}
	/**
	 * oracle 主键ID，通过序列生成
	 * @说明: TODO
	 * @方法名称: oraclePkId
	 * @参数 @param datasourceId
	 * @参数 @param tbName
	 * @参数 @return
	 * @返回类型 String    
	 * @创建者: 胡常建  
	 * @创建时间: 2021年2月19日 下午1:02:41
	 * @修改者: 胡常建  
	 * @修改时间: 2021年2月19日 下午1:02:41
	 */
    public static String oraclePkId(String datasourceId,String tbName) {
    	try {
    		String seqName=getSequenceNameByTableName(tbName);
    		String sql="select " + seqName + ".nextval seq from dual";
    		SQLExecutorDbMapper<Entity> dbMapper=SQLExecutorDbMapper.getInstance(datasourceId, Entity.class);
    		List<Entity> entities=dbMapper.findBySql(sql);
    		return entities.get(0).getStr("seq");
		} catch (Exception e) {
			throw new MFTException(e);
		}
    }
    
    
    public static String buildPageSQL(String datasourceId,String sql,int startRow, int rows) {
		Dialect dialect=DialectFactory.getDialect(DbProvider.getInstance().getDataSource(datasourceId));
		switch (DialectName.valueOf(dialect.dialectName())) {
		case MYSQL:
			StringBuilder builder=new StringBuilder();
			builder.append("select * from ( ");
			builder.append(sql);
			builder.append(" ) v limit ");
			builder.append(startRow);
			builder.append(",");
			builder.append(rows);
			return builder.toString();
		case ORACLE:
            StringBuilder sqlBuffer = new StringBuilder();
            sqlBuffer.append("select * from ( select row_.*, rownum rownum_ from ( ");
            sqlBuffer.append(sql);
            sqlBuffer.append(" ) row_ where rownum <= " + (startRow + rows) + ") where rownum_ > " + startRow + "");
            return sqlBuffer.toString();
		default:
			throw new MapperException("不支持的数据库类型，仅支持MYSQL和ORACLE");
		}
	
	}
	
	public static String buildCountSQL(String sql) {
		StringBuilder builder=new StringBuilder();
		builder.append("select count(*) total from ( ");
		builder.append(sql);
		builder.append(" ) v ");
		return builder.toString();
	}
}
